package com.inzone.fram.gray.common.rule;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.alibaba.cloud.nacos.ribbon.ExtendBalancer;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.xidea.el.Expression;
import org.xidea.el.impl.ExpressionImpl;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 命中元数据的负载均衡规则
 * 公共部分
 *
 * @author 丁常磊
 * @date 2021/8/23 16:19
 */
@Slf4j
public class MatchingMetadataRule extends AbstractLoadBalancerRule {

    @Autowired
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    @Autowired
    private NacosServiceManager nacosServiceManager;

    /**
     * 命中分组的正则
     */
    private static final String MATCHING_GROUP = "inzone-matching-group";

    /**
     * 命中标签表达式
     */
    private static final String MATCHING_TAG_EXPRESSION = "inzone-matching-tag-expression";

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object o) {
        //代码骨架源于 NacosRule
        try {
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            String group = this.nacosDiscoveryProperties.getGroup();
            DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) this.getLoadBalancer();
            String name = loadBalancer.getName();
            NamingService namingService = this.nacosServiceManager.getNamingService(this.nacosDiscoveryProperties.getNacosProperties());

            //获取该名称下所有的实例
            List<Instance> instances = namingService.selectInstances(name, group, true);
            if (CollectionUtils.isEmpty(instances)) {
                return null;
            } else {

                //检索服务实例
                List<Instance> instancesToChoose = instances;

                //使用元数据进行一次过滤
                instancesToChoose = filterByMetadata(instancesToChoose);

                //过滤后无可用实例
                if (CollectionUtils.isEmpty(instancesToChoose)) {
                    return null;
                }

                //如果配置了集群名称,优先选择相同集群下的
                if (StringUtils.isNotBlank(clusterName)) {
                    List<Instance> sameClusterInstances = instancesToChoose.stream().filter((instancex) -> Objects.equals(clusterName, instancex.getClusterName())).collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(sameClusterInstances)) {
                        instancesToChoose = sameClusterInstances;
                    }
                }

                //负载均衡
                Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToChoose);
                return new NacosServer(instance);
            }
        } catch (Exception var10) {
            return null;
        }
    }

    /**
     * 使用元数据过滤服务实例
     * <p>
     * 过滤原则
     * 1.优先级
     * group>tagMap
     * 2.元数据有对应的表达式才执行过滤
     *
     * @param instancesToChoose
     * @return
     */
    private List<Instance> filterByMetadata(List<Instance> instancesToChoose) {

        //如果当前未完成命中初始化直接结束
        if (!MatchingContext.isInitialized()) {
            return instancesToChoose;
        }

        /*
            过滤服务
             规则:
              1.如果有group正则,优先匹配group正则
              2.如果有tag表达式,则将tag头转变量执行表达式,如果表达式结果返回true则匹配
         */
        MatchingContext current = MatchingContext.current();
        List<String> groups = current.getGroups();
        Map<String, String> tagMap = current.getTagMap();

        //断定为不参与灰度的,筛选所有不参与灰度的实例
        if (groups.size() == 0 && tagMap.size() == 0) {

            List<Instance> list = instancesToChoose.stream().filter(instance -> {
                Map<String, String> metadata = instance.getMetadata();
                return metadata.get(MATCHING_GROUP) == null && metadata.get(MATCHING_TAG_EXPRESSION) == null;
            }).collect(Collectors.toList());

            //如果没有任何普通服务返回所有
            if (list.size() == 0) {
//                log.warn("未找到可用的普通服务,返回所有服务 groups={} | tagMap={}", groups, tagMap);
                return instancesToChoose;
            }

            return list;

        }

        //是否有可用的灰度实例
        boolean[] h = new boolean[]{false};

        //参与灰度的请求只选择参与灰度的实例
        List<Instance> list = instancesToChoose.stream().filter(instance -> {
            Map<String, String> metadata = instance.getMetadata();
            String group = metadata.get(MATCHING_GROUP);
            String exp = metadata.get(MATCHING_TAG_EXPRESSION);

            //不参与灰度请求的处理
            if (group == null && exp == null) {
                return false;
            }

            //有灰度服务
            h[0] = true;
            //必须两个都满足
            return checkByGroup(group, groups) && checkByTagMap(exp, tagMap);
        }).collect(Collectors.toList());


        //无灰度服务降级为普通服务
        if (!h[0]) {
            log.warn("未找到可用的灰度服务,返回所有服务 groups={} | tagMap={}", groups, tagMap);
            return instancesToChoose;
        }
        return list;

    }

    private boolean checkByTagMap(String exp, Map<String, String> tagMap) {

        //如果无表达式返回为命中
        if (StringUtils.isEmpty(exp)) {
            return true;
        }
        //执行表达式
        try {
            Expression expression = new ExpressionImpl(exp);
            Object evaluate = expression.evaluate(tagMap);
            if (evaluate instanceof Boolean) {
                return (Boolean) evaluate;
            } else {
                return false;
            }
        } catch (Exception e) {
            log.error("tag表达式命中服务时出错 tagMap={} | expression={}", tagMap, exp);
            e.printStackTrace();
            return false;
        }
    }

    private boolean checkByGroup(String group, List<String> groups) {

        //无分组表达式返回为命中
        if (StringUtils.isEmpty(group)) {
            return true;
        }
        return groups.contains(group);
    }

}
